<?php
/**
 * Copyright (c) 2018-2022.
 * This file is part of the moonpie production
 *   (c) johnzhang <875010341@qq.com>
 *   This source file is subject to the MIT license that is bundled
 *  with this source code in the file LICENSE.
 */

namespace Moonpie\Macro\HuaweiCloud\Kernel;


use EasyWeChat\Kernel\Contracts\AccessTokenInterface;
use EasyWeChat\Kernel\Events\AccessTokenRefreshed;
use EasyWeChat\Kernel\Exceptions\HttpException;
use EasyWeChat\Kernel\Exceptions\RuntimeException;
use EasyWeChat\Kernel\ServiceContainer;
use EasyWeChat\Kernel\Support\Arr;
use EasyWeChat\Kernel\Traits\HasHttpRequests;
use EasyWeChat\Kernel\Traits\InteractsWithCache;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class AccessToken implements AccessTokenInterface
{
    //默认使用密码
    const METHOD_PASSWORD = 'password';
    //委托方式,暂未实现
    const METHOD_ASSUME = 'assume_role';
    //使用密码+MFA模式
    const METHOD_MFA = 'totp';
    const HEADER_TOKEN = 'X-Subject-Token';
    const HEADER_AUTH = 'X-Auth-Token';
    /**
     * @var array
     */
    protected $token;
    protected $requestMethod = 'POST';


    /**
     * @var string
     */
    protected $tokenKey = 'token';

    /**
     * @var string
     */
    protected $cachePrefix = 'moonpie_macro.huaweicloud.access_token.';
    /**
     * @var ServiceContainer
     */
    protected $app;
    private   $iam;
    /**
     * @var array
     */
    private $project;
    use HasHttpRequests;
    use InteractsWithCache;

    public function __construct(ServiceContainer $app, $iam, $project = [])
    {
        $this->app     = $app;
        $this->iam     = $iam;
        $this->project = $project;
    }

    public function getToken($refresh = false): array
    {
        $cacheKey = $this->getCacheKey();
        $cache    = $this->getCache();

        if (!$refresh && $cache->has($cacheKey) && $result = $cache->get(
                $cacheKey
            )) {
            return $result;
        }

        /** @var array $token */
        $token = $this->requestToken($this->getCredentials(), true);

        $this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 86400);

        $this->token = $token;


        return $token;
    }

    /**
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function setToken(
        string $token,
        int $lifetime = 86400
    ): AccessTokenInterface {
        $this->getCache()->set(
            $this->getCacheKey(),
            [
                $this->tokenKey => $token,
                'expires_in'    => $lifetime,
            ],
            $lifetime
        );

        if (!$this->getCache()->has($this->getCacheKey())) {
            throw new RuntimeException('Failed to cache access token.');
        }

        return $this;
    }

    public function refresh(): AccessTokenInterface
    {
        $this->getToken(true);

        return $this;
    }

    /**
     * @param bool $toArray
     *
     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
     *
     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     */
    public function requestToken(array $credentials, $toArray = false)
    {
        $response  = $this->sendRequest($credentials);
        if(!$response->hasHeader(static::HEADER_TOKEN)) {
            throw new HttpException(
                'No Response Token Header'
            );
        }
        $result    = json_decode($response->getBody()->getContents(), true);
        $formatted = $this->castResponseToType(
            $response,
            $this->app['config']->get('response_type')
        );


        if (empty($result[$this->tokenKey])) {
            throw new HttpException(
                'Request access_token fail: '.json_encode(
                    $result,
                    JSON_UNESCAPED_UNICODE
                ), $response, $formatted
            );
        }
        $timezone = new \DateTimeZone('UTC');
        $deadline = \DateTimeImmutable::createFromFormat("Y-m-d\TH:i:s.u\Z", $result[$this->tokenKey]['expires_at'], $timezone);

        $now = new \DateTimeImmutable('now', $timezone);
        $lifetime = $deadline->getTimestamp() - $now->getTimestamp();
        $final = [
            $this->tokenKey => $response->getHeaderLine(static::HEADER_TOKEN),
            'expires_in' => $lifetime > 0 ? $lifetime : 86400,
        ];

        return $final;
    }

    public function applyToRequest(
        RequestInterface $request,
        array $requestOptions = []
    ): RequestInterface {
        $token = $this->getToken();
        return $request->withHeader(static::HEADER_AUTH, $token[$this->tokenKey]);
    }

    /**
     * @return string
     */
    protected function getCacheKey()
    {
        return $this->cachePrefix.md5(json_encode($this->getCredentials()));
    }

    protected function sendRequest(array $credentials): ResponseInterface
    {
        $options = [
            ('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials,
        ];
        if (!isset($options['query'])) {
            $options['query'] = [];
        }
        $options['query']['nocatalog'] = true;

        return $this->setHttpClient($this->app['http_client'])->request(
            $this->getEndpoint(),
            $this->requestMethod,
            $options
        );
    }

    protected function getCredentials()
    {
        $method = Arr::get($this->iam, 'role', static::METHOD_PASSWORD);
        $post   = [
            'auth' => [
                'identity' => [

                ],
                'scope'    => [

                ],
            ],
        ];
        if (!empty($this->project)) {
            if (isset($this->project['id'])) {
                $post['auth']['scope'] = [
                    'project' => [
                        'id' => $this->project['id'],
                    ],
                ];
            } else {
                if (isset($this->project['name'])) {
                    $post['auth']['scope'] = [
                        'project' => [
                            'name' => $this->project['name'],
                        ],
                    ];
                }
            }
        }else if (!empty($this->iam['domain'])) {
            if (isset($this->iam['domain']['id'])) {
                $post['auth']['scope'] = [
                    'domain' => [
                        'id' => $this->iam['domain']['id'],
                    ],
                ];
            } else {
                if (isset($this->iam['domain']['name'])) {
                    $post['auth']['scope'] = [
                        'domain' => [
                            'name' => $this->iam['domain']['name'],
                        ],
                    ];
                }
            }
        }
        switch ($method) {
            case static::METHOD_MFA:
                $post['auth']['identity']['methods'] = [
                    static::METHOD_PASSWORD, static::METHOD_MFA,
                ];
                $post['auth']['identity']['password'] = [
                    'user' => [
                        'name' => $this->iam['name'],
                        'password' => $this->iam['password'],
                        'domain' => [
                            'name' => $this->iam['domain']['name'],
                        ]
                    ]
                ];
                $post['auth']['identity']['totp'] = [
                    'user' => [
                        'id' => $this->iam['id'],
                        'passcode' => $this->iam['passcode'],
                    ]
                ];
                break;
            case static::METHOD_ASSUME:
                throw new RuntimeException('暂未实现的方式:'.$method);
            default:
                $post['auth']['identity']['methods'] = [
                    static::METHOD_PASSWORD,
                ];
                $post['auth']['identity']['password'] = [
                    'user' => [
                        'name' => $this->iam['name'],
                        'password' => $this->iam['password'],
                        'domain' => [
                            'name' => $this->iam['domain']['name'],
                        ]
                    ]
                ];
                break;
        }
        return $post;
    }
    public function getEndPoint()
    {
        $endpoint = '';
        if (isset($this->project['domain'])) {
            $endpoint = '.' . $this->project['domain'];
        }
        return strtr('https://iam{endpoint}.myhuaweicloud.com/v3/auth/tokens', [
            '{endpoint}' => $endpoint
        ]);
    }

}